﻿using System;
using ValidationFramework.Extensions;
using ValidationFramework.Reflection;

namespace ValidationFramework.Specifications
{

	/// <summary>
	/// Performs a string to number conversion validation.
	/// </summary>
	/// <remarks>If the value being validated is null the rule will evaluate to true.</remarks>
	/// <seealso cref="EnumConversionRuleConfigReader"/>
	/// <seealso cref="EnumConversionRuleAttribute"/>
#if (!SILVERLIGHT)
	[Serializable]
#endif
	public class IsConvertableToEnumSpec : SpecificationBase 
	{
		// --- Constructors

		/// <param name="enumType">The <see cref="Type"/> that will attempted to be converted to.</param>
		public IsConvertableToEnumSpec(Type enumType)
		{
			Guard.ArgumentNotNull(enumType, "enumType");
			EnumType = enumType;
			IgnoreCase = true;
		}


		// --- Properties



		/// <summary>
		/// Gets the <see cref="Type"/> that will attempted to be converted to.
		/// </summary>
		public Type EnumType
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets whether to ignore case. 
		/// </summary>
		/// <remarks>
		/// Only applicable if the property type is string.
		/// </remarks>
		public bool IgnoreCase
		{
			get;
			set;
		}


		// --- Methods



		/// <inheritdoc />
		public override bool IsSatisfiedBy(object target)
		{
			if (target == null)
			{
				return true;
			}
			else
			{
				return TypeExtensions.IsEnumDefined(EnumType, target, IgnoreCase);
			}
		}

		/// <inheritdoc />
		public override string DefaultMessage(string tokenizedMemberName, string descriptorType)
		{
			return string.Format("The {0} '{1}' is an invalid format.", descriptorType, tokenizedMemberName);
		}

		/// <inheritdoc />
		public override string ToString()
		{
			return "The value must be convertible to Enum.";
		}

		/// <inheritdoc />
		public override bool IsEquivalentTo(ISpecification spec)
		{
			var ecSpecification = (IsConvertableToEnumSpec)spec;
			return ecSpecification.EnumType == EnumType;
		}

		/// <inheritdoc />
		public override bool CanBeSatisfiedBy(Type typeToTest)
		{
			const string invalidTypeFormat = "Specification cannot test type: '{0}'. It must be a byte, short, int, long, ushort, ulong, uint or string.";

			var targetTypeHandle = typeToTest.TypeHandle;
			if (!TypePointers.IsNumericType(targetTypeHandle) && !targetTypeHandle.Equals(TypePointers.StringTypeHandle))
			{
				var targetMemberRuntimeType = Type.GetTypeFromHandle(targetTypeHandle);
				var friendlyTypeName = targetMemberRuntimeType.ToUserFriendlyString();
				var exceptionMessage = string.Format(invalidTypeFormat, friendlyTypeName);
				throw new ArgumentException(exceptionMessage, "value");
			}
			return true;
		}

	}
}